﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;


namespace SerialBrowserBridge.WebServer
{
    /// <summary>
    /// Represent a parsed request
    /// </summary>
    public class Request
    {
        /// <summary>
        /// Method of request (only get or post are supported)
        /// </summary>
        public String Method { get; set; }

        /// <summary>
        /// URL
        /// </summary>
        public String URL { get; set; }

        /// <summary>
        /// HTTP-Version 
        /// </summary>
        public String Version { get; set; }
        
        /// <summary>
        /// Parameters of HTTP request (maybe this is an empty dictionary)
        /// </summary>
        public Dictionary<string, string> Parameters { get; set; }


        private Dictionary<String,String> header;

        /// <summary>
        /// Headlines of HTTP request
        /// </summary>
        public Dictionary<String,String> Header
        {
            get { return header; }
            set { header = value; }
        }

        /// <summary>
        /// Body of request
        /// </summary>
        public String Content { get; set; }

        /// <summary>
        /// Create a new request object based on give text
        /// </summary>
        /// <param name="text">whole request string</param>
        /// <returns>request object or null</returns>
        public static Request Parse(String text)
        {
            if (text.Length < 4)
                return null;

            var result = new Request { 
                Header = new Dictionary<string, string>(), 
                Parameters = new Dictionary<string,string>() 
            };

            var index = 0;
            var length = 0;

            // Get requested method
            length = new[] { text.Length, text.IndexOf(' ', index), text.IndexOf("\r\n", index) }.Min();
            if (length == -1)
                return null;

            var method = text.Substring(0, length).ToUpper();
            if (method.StartsWith("GET"))
                result.Method = "get";
            else if (method.StartsWith("POST"))
                result.Method = "post";
            else
                return null;
            
            index = length + 1;
            if (index > text.Length)
                return null;

            // Get URL
            length = new[] { text.Length, text.IndexOf(' ', index), text.IndexOf("\r\n", index) }.Min();
            result.URL = URLDecode(text.Substring(index, length - index));
            index = text[index] == '\r' ? length + 2 : length + 1;
            if (index > text.Length)
                return null;

            // Get HTTP version
            length = new[] { text.Length, text.IndexOf("\r\n", index) }.Min();
            result.Version = text.Substring(index, length - index);
            index = text[length] == '\r' ? length + 2 : length + 1;
            if (index > text.Length)
                return null;

            // Get header lines
            while (!text.Substring(index).StartsWith("\r\n"))
            {
                var key = String.Empty;
                var value = String.Empty;

                length = new[] { text.Length, text.IndexOf(':', index), text.IndexOf("\r\n", index) }.Min();
                if (text[length] == ':')
                {
                    key = text.Substring(index, (length - index) );
                    index = length + 1;
                }

                length = new[] { text.Length, text.IndexOf("\r\n", index) }.Min();

                if (text[length] == '\r')
                {
                    value = text.Substring(index, (length - index));
                    index = length + 2;
                }

                if (!String.IsNullOrEmpty(key))
                    result.Header.Add(key, value);

                if (index > text.Length)
                    return null;
            }

            // End of header
            index += 2;
            if (index > text.Length)
                return result;

            // Parse parameters depend on http method
            if (result.Method == "get" && result.URL.IndexOf('?') != -1)
            {
                result.Parameters = StringToParameterDictionary(result.URL, true);
            }
            else if (result.Method == "post" && result.Header["Content-type"] == "application/x-www-form-urlencoded")
            {
                result.Parameters = StringToParameterDictionary(text.Substring(index), false);
            }

            return result;

        }


        /// <summary>
        /// Converts parameter to dictionary
        /// </summary>
        /// <param name="text">text to convert</param>
        /// <param name="withQuestionmark">has a given text a leading questionmark</param>
        /// <returns>Dictionary</returns>
        private static Dictionary<String, String> StringToParameterDictionary(String text, bool withQuestionmark)
        {
            var result = new Dictionary<String, String>();
            if (String.IsNullOrEmpty(text))
                return result;

            var index = 0;
            var length = 0;

            if (!text.Contains('?') && withQuestionmark)
                return result;

            // "?"
            length = withQuestionmark ? new[] { text.Length, text.IndexOf('?') }.Min() : 0;
            if (withQuestionmark) 
                index = length + 1;
            if (index > text.Length) 
                return result;

            var parameters = text.Substring(index);
            if (String.IsNullOrEmpty(parameters))
                return result;

            return (    from parameter in parameters.Split('&') 
                        let keyValue = parameter.Split('=') 
                        select new { 
                            Key = keyValue[0], 
                            Value = keyValue[1] 
                        }
                    ).ToDictionary(key => key.Key, value => value.Value);
        }


        /// <summary>
        /// Decode url based on RFC 1738
        /// </summary>
        /// <param name="text"></param>
        /// <returns></returns>
        public static String URLDecode(String text)
        {
            if (String.IsNullOrEmpty(text) || text.IndexOf('%') == -1)
                return text;

            // http://www.blooberry.com/indexdot/html/topics/urlencoding.htm
            var mapping = new Dictionary<String, String>{
                {"24", "$"},
                {"26", "&"},
                {"2B", "+"},
                {"2C", ","},
                {"2F", "/"},
                {"3A", ":"},
                {"3B", ";"},
                {"3D", "="},
                {"3F", "?"},
                {"40", "@"},
                {"20", " "},
                {"22", "\""},
                {"3C", "<"},
                {"3E", ">"},
                {"23", "#"},
                {"25", "%"},
                {"7B", "{"},
                {"7D", "}"},
                {"7C", "|"},
                {"5C", "\\"},
                {"5E", "^"},
                {"7E", "~"},
                {"5B", "["},
                {"5D", "]"},
                {"60", "`"}
            };

            var result = new StringBuilder();
            var index = 0;
            do
            {
                var newIndex = text.IndexOf('%', index);
                if (newIndex == -1)
                    result.Append(text.Substring(index, text.Length - index));
                else
                {
                    result.Append(text.Substring(index, newIndex - index));
                    var code = text.Substring(newIndex + 1, Math.Min(text.Length - newIndex, 2));
                    if (!String.IsNullOrEmpty(code))
                    {
                        result.Append(mapping[code]);

                        newIndex += code.Length + 1;
                    }
                }


                index = newIndex;
            } while (index != -1);

            return result.ToString();
        }

        /// <summary>
        /// Check if parameter is available
        /// </summary>
        /// <param name="name">Keyname</param>
        /// <returns>true=available, false=not available</returns>
        public bool HasParameter(String name)
        {
            return Parameters.ContainsKey(name);
        }

    }
}
